﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Configuration;
using System.ServiceModel.PeerResolvers;
using System.Xml.Serialization;
using DeSleeper.Library;

namespace DeSleeper.Service
{
    public class ConfigurationBuilder
    {
        private Configuration _configuration;
        private ServiceModelSectionGroup _serviceModelGroup;
        internal const string _peerDefaultBindingName = "PeerDefault";
        internal const string _peerClientEndpointName = "PeerProxyRegister";
        internal const string _securedDefaultBindingName = "SecuredDefault";
        internal static readonly string _wakeUpServiceName = typeof(WakeUpService).FullName;
        internal static readonly string _registerServiceName = typeof(ProxyRegister).FullName;
        internal const string _defaultBehaviorName = "DeSleeper.DefaultBehavior";
        internal static Uri _defaultWakeUpAddress = new Uri("http://localhost/");
        internal const string _defaultMeshAddress = "net.p2p://DeSleeperMesh";

        public ConfigurationBuilder() {}
        public ConfigurationBuilder(Configuration configuration)
        {
            _configuration = configuration;
        }


        protected ServiceModelSectionGroup ServiceModelGroup
        {
            get
            {
                if (_configuration == null) _configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
                if (_serviceModelGroup == null) _serviceModelGroup = _configuration.SectionGroups["system.serviceModel"] as ServiceModelSectionGroup;
                return _serviceModelGroup;
            }
        }

        public static string PeerClientEndpointName
        {
            get { return _peerClientEndpointName; }
        }

        #region Direct Setup
        public void SetupWakeUpService(Uri address)
        {
            var binding = setupDirectBinding();
            var behavior = setupDefaultBehaviorElement();
            var service = setupWakeUpServiceElement(behavior, binding);
        	
			Uri baseAddress;
			if (Uri.TryCreate(address ?? _defaultWakeUpAddress, "WakeUpService", out baseAddress))
			{
				service.Host.BaseAddresses.Clear();
				service.Host.BaseAddresses.Add(new BaseAddressElement { BaseAddress = baseAddress.ToString() });
			}
        }

        private WSHttpBindingElement setupDirectBinding()
        {
            if (ServiceModelGroup.Bindings.WSHttpBinding.Bindings.ContainsKey(_securedDefaultBindingName))
                return ServiceModelGroup.Bindings.WSHttpBinding.Bindings[_securedDefaultBindingName];

            var newBinding = new WSHttpBindingElement(_securedDefaultBindingName);
            ServiceModelGroup.Bindings.WSHttpBinding.Bindings.Add(newBinding);
            return newBinding;
        }

        private ServiceElement setupWakeUpServiceElement(ServiceBehaviorElement behaviorElement, IBindingConfigurationElement bindingElement)
        {
            if (IsWakeUpServiceConfigured())
                return ServiceModelGroup.Services.Services[_wakeUpServiceName];

            var newService =
                new ServiceElement(_wakeUpServiceName)
                    {
                        BehaviorConfiguration = behaviorElement.Name,
                        Endpoints =
                            {
                                new ServiceEndpointElement(new Uri(string.Empty, UriKind.Relative),
                                                           typeof (IWakeUpService).FullName)
                                    {
                                        Name = "WakeUpService",
                                        BindingConfiguration = bindingElement.Name,
                                        Binding = "wsHttpBinding"
                                    },
                                new ServiceEndpointElement(new Uri("mex", UriKind.Relative), "IMetadataExchange")
                                    {Binding = "mexHttpBinding"}
                            },
                    };
            ServiceModelGroup.Services.Services.Add(newService);
            return newService;
        }

        public bool IsWakeUpServiceConfigured()
        {
            return ServiceModelGroup.Services.Services.ContainsKey(_wakeUpServiceName);
        }
        #endregion


        #region Peer Setup
        public void SetupPeerService(Uri address)
        {
            var binding = setupPeerBinding();
            var behavior = setupDefaultBehaviorElement();
            var service = setupRegisterServiceElement(behavior, binding);

			Uri baseAddress;
			if (Uri.TryCreate(address ?? _defaultWakeUpAddress, "RegisterService", out baseAddress))
			{
				service.Host.BaseAddresses.Clear();
				service.Host.BaseAddresses.Add(new BaseAddressElement {BaseAddress = baseAddress.ToString()});
				var client = setupPeerClientElement(new EndpointAddress(baseAddress), binding);
			}
        }

        private NetPeerTcpBindingElement setupPeerBinding()
        {
            if (ServiceModelGroup.Bindings.NetPeerTcpBinding.Bindings.ContainsKey(_peerDefaultBindingName))
                return ServiceModelGroup.Bindings.NetPeerTcpBinding.Bindings[_peerDefaultBindingName];

            var newBinding =
                new NetPeerTcpBindingElement(_peerDefaultBindingName)
                    {
                        Resolver = {Mode = PeerResolverMode.Auto, ReferralPolicy = PeerReferralPolicy.Service},
                        Security = {Transport = {CredentialType = PeerTransportCredentialType.Password}}
                    };

            ServiceModelGroup.Bindings.NetPeerTcpBinding.Bindings.Add(newBinding);
            return newBinding;
        }

        private ServiceElement setupRegisterServiceElement(ServiceBehaviorElement behaviorElement, IBindingConfigurationElement bindingElement)
        {
            if (!IsRegisterServiceConfigured())
            {
                var newService = new ServiceElement(_registerServiceName)
                {
                    BehaviorConfiguration = behaviorElement.Name,
                    Endpoints =
                                             {
                                                 new ServiceEndpointElement(new Uri(""),
                                                                            typeof (IProxyRegister).FullName)
                                                     {
                                                         Name = "ProxyRegister",
                                                         BindingConfiguration = bindingElement.Name,
                                                         Binding = "netPeerTcpBinding"
                                                     }
                                             },
                };
                ServiceModelGroup.Services.Services.Add(newService);
                return newService;
            }

            return ServiceModelGroup.Services.Services[_registerServiceName];
        }

        private ChannelEndpointElement setupPeerClientElement(EndpointAddress address, IBindingConfigurationElement bindingElement)
        {
			var newEndpoint = new ChannelEndpointElement(address, typeof(IProxyRegister).FullName) { Name = _peerClientEndpointName, Binding = "netPeerTcpBinding", BindingConfiguration = bindingElement.Name };
        	var endpointIndex = ServiceModelGroup.Client.Endpoints.IndexOf(newEndpoint);
			if(endpointIndex != -1)
				return ServiceModelGroup.Client.Endpoints[endpointIndex];

			ServiceModelGroup.Client.Endpoints.Add(newEndpoint);
			return newEndpoint;
        }

        public bool IsRegisterServiceConfigured()
        {
            return ServiceModelGroup.Services.Services.ContainsKey(_registerServiceName);
        }
        #endregion

        #region Live Mesh Setup
        public void SetupLiveMesh(string userID, string password, string cloudURI)
        {
            var settings = new CloudSettings {UserID = userID, Password = password, Uri = cloudURI};

            string settingsString;
            var serializer = new XmlSerializer(typeof(CloudSettings));
            using (var writer = new StringWriter())
            {
                serializer.Serialize(writer, settings);
                settingsString = writer.ToString();
            }

            if (_configuration.AppSettings.Settings.AllKeys.Contains("CloudSettings"))
                _configuration.AppSettings.Settings["CloudSettings"].Value = settingsString;
            else
                _configuration.AppSettings.Settings.Add("CloudSettings", settingsString);
        }
        #endregion

        private ServiceBehaviorElement setupDefaultBehaviorElement()
        {
            if (!ServiceModelGroup.Behaviors.ServiceBehaviors.ContainsKey(_defaultBehaviorName))
            {
                var newBehavior = new ServiceBehaviorElement(_defaultBehaviorName)
                                      {
                                          new ServiceMetadataPublishingElement {HttpGetEnabled = true},
                                          new ServiceDebugElement {IncludeExceptionDetailInFaults = false},
                                          new ServiceCredentialsElement
                                              {WindowsAuthentication = {AllowAnonymousLogons = false}}
                                      };
                ServiceModelGroup.Behaviors.ServiceBehaviors.Add(newBehavior);
                return newBehavior;
            }

            return ServiceModelGroup.Behaviors.ServiceBehaviors[_defaultBehaviorName];
        }

        public void Save()
        {
            _configuration.Save(ConfigurationSaveMode.Minimal);
        }
    }
}